#include "Position.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <wincrypt.h>
#include <list>
#include <iostream>
#include <WinSock2.h>
#include <gw2api.h>

#define PI 3.14159265
#define BUFFER_SIZE 2048
#define NUM_SIZE 256
#define CHARNAME_LEN 42
#define DESCRIPTION "\n\t\t\t\t\t\t\tGw2Maps\n\t\t\t\t\t\tCharacter Data\n\t\t\tExample:\tProfession:\tfstCharName,sndCharName,...;\nmapping:\tUnknown:=0, Engineer:=1, Necromancer:=2, Thief:=3, Elementalist:=4, Warrior:=5, Ranger:=6, Mesmer:=7, Guardian:=8\n----------------------------------------------------------------------------------------------------------\n"
#define ENGINEER "Engineer:"
#define NECROMANCER "Necromancer:"
#define THIEF "Thief:"
#define ELEMENTALIST "Elementalist:"
#define WARRIOR "Warrior:"
#define RANGER "Ranger:"
#define MESMER "Mesmer:"
#define GUARDIAN "Guardian:"
#define J_OPEN_OBJ "data={"
#define J_CLOSE_OBJ "}"
#define J_OPEN_ARR "["
#define J_CLOSE_ARR "]"
#define J_SEPARATOR ","
#define J_VERSION "\"version\":"
#define J_AVATAR_POSITION "\"avatar_position\":"
#define J_CHARACTER_DATA "\"character_data\":"
#define J_MAP_ID "\"map_id\":"
#define J_SERVER_ID "\"world_id\":"
#define J_AVATAR_FRONT "\"avatar_front\":"
#define J_GUILD_ID "\"guild_id\":"
#define J_ACCOUNT_NAME "\"account_name\":"
#define J_CHANNEL_ID "\"channel_id\":"
#define J_PROFESSION "\"profession\":"
#define J_TRACKING "\"tracking\":"
#define J_COMMANDER "\"commander\":"
#define J_IPADDR "\"server_ip\":"
#define J_REGIONID "\"region_id\":"
#define J_BUILD "\"build\":"
#define ENCODING_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)
#define MD5LEN 16
#define SHALEN 32

char* encrypt(char*);
DWORD WINAPI updateCharacterSettings(LPVOID);
int toUTF8(char*, char*, int);


Position::Position(char *path)
{
	initialized = false;
	tracking = false;
	commander = false;
	guildId = 0;
	channelId = 0;
	lastUpdate = 0;
	distance = 0;
	updateTime = 0;
	updateThread = 0;
}


Position::~Position(void)
{
}


bool Position::initializeSharedMemory(){
	hMemory = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(struct LinkedMem), TEXT(SHARED_MEMORY));
	if(hMemory == NULL)
		return false;
	memory = (struct LinkedMem*)MapViewOfFile(hMemory, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(struct LinkedMem));
	memset(memory, 0, sizeof(struct LinkedMem));
	memset(&image, 0, sizeof(struct LinkedMem));
	initialized = true;
	return true;
}


char* Position::createSnapshot(){
	char *buffer; 
	struct in_addr addr;
	char num[NUM_SIZE], name[NUM_SIZE], c;
	int state;
	std::map<const char*, enum profession, characterComperator>::iterator it;
	auto resetNum = [&]()->void{memset(num, '\0', NUM_SIZE);};
	auto mod = [](int arg1, int arg2)->int{return arg1 < 0? arg1+arg2 : arg1;};
	if(!initialized || memcmp(&image, memory, sizeof(struct LinkedMem)) == 0)//recheck if update's really neccessary
		return 0;
	buffer = (char*)malloc(BUFFER_SIZE);
	memcpy(&image, memory, sizeof(struct LinkedMem));
	lastUpdate = clock();
	memset(buffer, '\0', BUFFER_SIZE);
	resetNum();
	strcpy_s(buffer, BUFFER_SIZE, J_OPEN_OBJ);

	//Version
	strcat_s(buffer, BUFFER_SIZE, J_VERSION);
	itoa(image.uiVersion, num, 10);
	strcat_s(buffer, BUFFER_SIZE, num);
	resetNum();
	strcat_s(buffer, BUFFER_SIZE, J_SEPARATOR);

	//Avatar Position
	sprintf_s(num, NUM_SIZE, "%s[%g,%g],", J_AVATAR_POSITION, INCHTOM(image.fAvatarPosition[0]), INCHTOM(image.fAvatarPosition[2]));
	strcat_s(buffer, BUFFER_SIZE, num);
	resetNum();

	//Avatar View
	sprintf_s(num, NUM_SIZE, "%s%d,", J_AVATAR_FRONT, mod((atan2(image.fAvatarFront[2], image.fAvatarFront[0])*180/PI), 360));
	strcat_s(buffer, BUFFER_SIZE, num);
	resetNum();

	//Char Data
	wcstombs(num, image.identity, NUM_SIZE);
	memset(name, '\0', NUM_SIZE);
	for(int count = 0, pos=0; num[count] != '\0'; count++, pos++){
		if((u_char)num[count] >= 0xc0){
			name[pos] = 0xc3;
			pos++;
			name[pos] = num[count]-0x40;
		}
		else
			name[pos] = num[count];
	}
	strcat_s(buffer, BUFFER_SIZE, J_CHARACTER_DATA);
	auto changeCommander = [&](char insert, int pos){memset(&name[strlen(name)-pos], 0, pos);name[strlen(name)] = insert;name[strlen(name)] = '}';};
	char set = memcmp(&name[strlen(name)-6], "true", 4) == 0 ? '1' : '0';
	set == '1' ? changeCommander(set, 6):changeCommander(set, 7);
	strcat_s(buffer, BUFFER_SIZE, name);
	strcat_s(buffer, BUFFER_SIZE, ",");
	
	//build
	sprintf_s(num, NUM_SIZE, "%s%hu,", J_BUILD, *(unsigned int*)&image.context[44]);
	strcat_s(buffer, BUFFER_SIZE, num);
	resetNum();

	//account name
	strcat_s(buffer, BUFFER_SIZE, J_ACCOUNT_NAME);
	strcat_s(buffer, BUFFER_SIZE, accountName);

	//guild id
	strcat_s(buffer, BUFFER_SIZE, ",");
		strcat_s(buffer, BUFFER_SIZE, J_GUILD_ID);
	if(guildId != 0)
		strcat_s(buffer, BUFFER_SIZE, guildId);
	else
		strcat_s(buffer, BUFFER_SIZE, "\"\"");

	//channel id
	strcat_s(buffer, BUFFER_SIZE, ",");
	strcat_s(buffer, BUFFER_SIZE, J_CHANNEL_ID);
	if(channelId != 0)
		strcat_s(buffer, BUFFER_SIZE, channelId);
	else
		strcat_s(buffer,BUFFER_SIZE, "\"\"");

	//tracking
	strcat_s(buffer, BUFFER_SIZE, ",");
	strcat_s(buffer, BUFFER_SIZE, J_TRACKING);
	strcat_s(buffer, BUFFER_SIZE, tracking?"1":"0");

	//end
	strcat_s(buffer, BUFFER_SIZE, J_CLOSE_OBJ);
	return buffer;
}


void Position::setGuildId(const char *id){
	if(guildId != 0){
		free(guildId);
		guildId = 0;
	}
	if(id == 0)
		return;
	guildId = (char*)malloc(128);
	memset(guildId, '\0', 128);
	sprintf_s(guildId, 128, "\"%s\"", id);
}


void Position::setAccountName(const char *accountName){
	memset(this->accountName, '\0', 128);
	sprintf_s(this->accountName, 128, "\"%s\"", accountName);
}


void Position::setChannelId(const char *id){
	char *buffer;
	int size;
	if(channelId != 0){
		free(channelId);
		channelId = 0;
	}
	if(id != 0){
		buffer = encrypt((char*)id);
		if(buffer != 0){
			size = strlen(buffer)+5;
			channelId = (char*)malloc(size);
			sprintf_s(channelId, size, "\"%s\"", buffer);
			free(buffer);
		}
	}
}


char* encrypt(char *c_str){
	HCRYPTPROV hProv = 0;
    HCRYPTHASH hHash = 0;
	BYTE *rgbHash = new BYTE[SHALEN];
	DWORD rgbHashLen = SHALEN;
	char lookup[] = "0123456789abcdef";
	char *result, *buffer;
	int size;

	if(!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
		return 0;
	if(!CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)){
		CryptReleaseContext(hProv,0);
		return 0;
	}
	size = strlen(c_str);
	for(int i = 0; i < size; i++){
		if(!CryptHashData(hHash, (BYTE*)&c_str[i], 1, 0)){
			CryptDestroyHash(hHash);
			CryptReleaseContext(hProv, 0);
			return 0;
		}
	}
	result = 0;
retry:
	if(CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &rgbHashLen, 0)){
		result = (char*)malloc(rgbHashLen*2+1);
		for(DWORD i = 0, pos = 0; i < rgbHashLen; i++, pos += 2){
			sprintf_s(&result[pos], 3, "%c%c", lookup[rgbHash[i] >> 4], lookup[rgbHash[i] & 0xf]);
		}
	}
	else if(GetLastError() == ERROR_MORE_DATA){
		rgbHash = new BYTE[rgbHashLen];
		goto retry;
	}
	CryptDestroyHash(hHash);
	CryptReleaseContext(hProv, 0);
	return result;
}


bool Position::clearForUpdate(){
	float check[2];
	if(tracking){
		check[0] = INCHTOM((memory->fAvatarPosition[0]-image.fAvatarPosition[0]));
		check[1] = INCHTOM((memory->fAvatarPosition[2]-image.fAvatarPosition[2]));
		return sqrt(check[0]*check[0]+check[1]*check[1]) >= distance && updateTime - (clock()-lastUpdate)/CLOCKS_PER_SEC <= 0;
	}
	else
		return updateTime - (clock()-lastUpdate)/CLOCKS_PER_SEC <= 0;
}


char* Position::getMessage(){
	char *result, character[256], u8_character[256];
	char *prof[] = {"unknown", "guardian", "warrior", "engineer", "ranger", "thief", "elementalist", "mesmer", "necromancer"};
	int buffer;
	result = (char*)malloc(256);
	wcstombs(character, memory->identity, 256);
	toUTF8(u8_character, character, 256);
	jPtr json = _parseJsonStr(u8_character);
	if(json != 0){
		sprintf_s(result, 256, "ID: %s\n%s, %s", accountName, json[0].string, prof[json[1].i]);
		_free_jptr(json);
	}
	else
		sprintf_s(result, 256, "ID: %s", accountName);
	return result;
}


int toUTF8(char *dest, char *src, int size){
	int pos = 0;
	for(int count = 0; src[count] != '\0' && pos < size; count++, pos++){
		if((u_char)src[count] >= 0xc0){
			dest[pos] = 0xc3;
			pos++;
			dest[pos] = src[count]-0x40;
		}
		else
			dest[pos] = src[count];
	}
	dest[pos] = 0;
	return pos;
}